En langage Swift, les indices (ou "indices de collection") sont des valeurs qui indiquent la position d'un élément dans une collection (par exemple, un tableau ou une chaîne de caractères).

Les indices sont utilisés pour accéder aux éléments individuels de la collection et pour effectuer des opérations telles que l'ajout, la suppression ou la modification d'éléments.

Voici un premier exemple :


let names = ["Alice", "Bob", "Charlie", "Dave", "Eve"]

Nous pouvons accéder à un élément individuel du tableau en utilisant un index. Les indices commencent à 0 pour le premier élément, 1 pour le deuxième élément, et ainsi de suite... Par exemple, pour accéder au deuxième élément (qui est "Bob"), nous pouvons utiliser l'index (ou l'indice) 1 :


let secondName = names[1]
print(secondName) 
// "Bob"

Nous pouvons également utiliser des indices pour modifier des éléments dans le tableau. Par exemple, si nous voulons changer le quatrième nom (qui est "Dave") en "Daniel", nous pouvons utiliser l'index 3 :


names[3] = "Daniel"
print(names) 
// ["Alice", "Bob", "Charlie", "Daniel", "Eve"]

Enfin, nous pouvons utiliser des boucles avec des indices pour parcourir tous les éléments d'un tableau et effectuer une opération sur chacun d'entre eux. Par exemple, pour afficher tous les noms dans le tableau, nous pouvons utiliser une boucle for avec un index :


for i in 0..<names.count {
    print(names[i])
}
// "Alice"
// "Bob"
// "Charlie"
// "Daniel"
// "Eve"

Dans cet exemple, nous utilisons une boucle for avec la plage 0..<names.count pour parcourir tous les indices valides dans le tableau. À chaque itération, nous accédons à l'élément du tableau correspondant à l'index actuel (en utilisant names[i]) et nous l'affichons à l'écran.


Les classes, les structures et les énumérations peuvent définir des indices , qui sont des raccourcis pour accéder aux éléments membres d'une collection, d'une liste ou d'une séquence. Vous utilisez des indices pour définir et récupérer des valeurs par index. Par exemple, vous accédez aux éléments d'un Array en tant que Array[index] et aux éléments d'un Dictionary en tant que Dictionary[key].

Les indices ne sont pas limités à une seule dimension et vous pouvez définir des indices avec plusieurs paramètres d'entrée.

Syntaxe d'indice

Les indices vous permettent d'interroger les instances d'un type en écrivant une ou plusieurs valeurs entre crochets après le nom de l'instance. Leur syntaxe est similaire à la fois à la syntaxe de méthode d'instance et à la syntaxe de propriété calculée. Vous écrivez des définitions d'indice avec le mot-clé subscript et spécifiez un ou plusieurs paramètres d'entrée et un type de retour, de la même manière que les méthodes d'instance.

Les indices peuvent être en lecture-écriture ou en lecture seule. Ce comportement est communiqué par un getter et un setter de la même manière que pour les propriétés calculées :


subscript(index: Int) -> Int {
    get {
        // Renvoyez une valeur d'indice appropriée ici.
    }
    set(newValue) {
        // Effectuez ici une action de modification appropriée.
    }
}

Le type de newValue est le même que la valeur de retour de l'indice. Comme pour les propriétés calculées, vous pouvez choisir de ne pas spécifier le paramètre newValue du setter. Un paramètre par défaut appelé newValue est fourni à votre setter si vous n'en fournissez pas vous-même.

Comme pour les propriétés calculées en lecture seule, vous pouvez simplifier la déclaration d'un indice en lecture seule en supprimant le mot-clé cet et ses accolades :


subscript(index: Int) -> Int {
    // Renvoyez une valeur d'indice appropriée ici.
}

Voici un exemple d'implémentation d'un indice en lecture seule, qui définit une structure TimesTable :


struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}

let threeTimesTable = TimesTable(multiplier: 3)
print("6 fois 3 est égale à \(threeTimesTable[6])")
// "6 fois 3 est égale à 18"

Dans cet exemple, une nouvelle instance de TimesTable est créée pour représenter la table de multiplication par trois. Ceci est indiqué en passant une valeur de 3 à la structure comme valeur pour le paramètre multiplier de l'instance.

Vous pouvez interroger l'instance threeTimesTable en appelant son indice, comme indiqué dans l'appel : threeTimesTable[6]. Cela demande la sixième entrée dans la table de multiplication par trois, qui renvoie une valeur de 18, ou 3 fois 6.

Une table de multiplication est basée sur une règle mathématique fixe. Il n'est pas approprié de définir une nouvelle valeur threeTimesTable, et donc l'indice pour TimesTable est défini comme un indice en lecture seule.

Utilisation de l'indice

La signification exacte de « indice » dépend du contexte dans lequel il est utilisé. Les indices sont généralement utilisés comme raccourcis pour accéder aux éléments membres d'une collection, d'une liste ou d'une séquence. Vous êtes libre d'implémenter les indices de la manière la plus appropriée pour la fonctionnalité de votre classe ou structure particulière.

Par exemple, le type Dictionary de Swift implémente un indice pour définir et récupérer les valeurs stockées dans une instance Dictionary. Vous pouvez définir une valeur dans un dictionnaire en fournissant une clé du type de clé du dictionnaire entre crochets en indice et en attribuant une valeur du type de valeur du dictionnaire à l'indice :


var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

L'exemple ci-dessus définit une variable appelée numberOfLegs et l'initialise avec un littéral de dictionnaire contenant trois paires clé-valeur. Le type du dictionnaire numberOfLegs est supposé être [String: Int]. Après avoir créé le dictionnaire, cet exemple utilise l'affectation d'indices pour ajouter une clé de type String et une valeur de type Int au dictionnaire.

Le type Dictionary de Swift implémente son indice clé-valeur en tant qu'indice qui prend et renvoie un type optionnel ou facultatif. Pour le dictionnaire numberOfLegs ci-dessus, l'indice clé-valeur prend et renvoie une valeur de type Int?, ou "int facultatif". Le type Dictionary utilise un type d'indice facultatif pour modéliser le fait que toutes les clés n'auront pas de valeur et pour donner un moyen de supprimer une valeur pour une clé en attribuant une valeur Nil à cette clé.

Options d'indice

Les indices peuvent prendre n'importe quel nombre de paramètres d'entrée, et ces paramètres d'entrée peuvent être de n'importe quel type. Les indices peuvent également renvoyer une valeur de n'importe quel type.

Comme les fonctions, les indices peuvent accepter un nombre variable de paramètres et fournir des valeurs par défaut pour leurs paramètres. Cependant, contrairement aux fonctions, les indices ne peuvent pas utiliser de paramètres in-out.

Une classe ou une structure peut fournir autant d'implémentations d'indices qu'elle en a besoin, et l'indice approprié à utiliser sera déduit en fonction des types de valeur ou de valeurs contenues dans les crochets d'indice au moment où l'indice est utilisé. Cette définition d'indices multiples est connue sous le nom de surcharge d'indices.

Bien qu'il soit plus courant qu'un indice prenne un seul paramètre, vous pouvez également définir un indice avec plusieurs paramètres si cela convient à votre type. L'exemple suivant définit une structure Matrix, qui représente une matrice de valeurs à deux dimensions Double. L'indice de la structure Matrix prend deux paramètres entiers :


struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index hors plage")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index hors plage")
            grid[(row * columns) + column] = newValue
        }
    }
}

Matrix fournit un initialiseur qui prend deux paramètres appelés rows et columns et crée un tableau suffisamment grand pour stocker rows * columns des valeurs de type Double. Chaque position dans la matrice reçoit une valeur initiale de 0.0. Pour ce faire, la taille du tableau, et une valeur de cellule initiale de 0.0, sont transmises à un initialiseur de tableau qui crée et initialise un nouveau tableau de la taille correcte.

Vous pouvez construire une nouvelle instance Matrix en transmettant un nombre approprié de lignes et de colonnes à son initialiseur :


var matrix = Matrix(rows: 2, columns: 2)

L'exemple ci-dessus crée une nouvelle instance Matrix avec deux lignes et deux colonnes. Le tableau instance Matrix de cette instance Matrix est en fait une version aplatie de la matrice, telle que lue du haut à gauche vers le bas à droite :

Utilisation d'un indice en swift
Les indices Swift - Source Apple

Les valeurs de la matrice peuvent être définies en transmettant des valeurs de ligne et de colonne dans l'indice, séparées par une virgule :


matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

Ces deux instructions appellent le setter de l'indice pour définir une valeur de 1.5 dans la position supérieure droite de la matrice (où row vaut 0 et column vaut 1) et 3.2 dans la position inférieure gauche (où row vaut 1 et column vaut 0) :

Utilisation d'un indice en swift
Les indices Swift - Source Apple

Le getter et le setter de l'indice Matrix contiennent tous deux une assertion pour vérifier que les valeurs de ligne et de colonne de l'indice sont valides Pour faciliter ces assertions, Matrix inclut une méthode pratique appelée indexIsValid(row:column:), qui vérifie si les requêtes row et column sont à l'intérieur des limites de la matrice :


func indexIsValid(row: Int, column: Int) -> Bool {
    return row >= 0 && row < rows && column >= 0 && column < columns
}

Une assertion est déclenchée si vous essayez d'accéder à un indice situé en dehors des limites de la matrice :


let someValue = matrix[2, 2]
// Cela déclenche une assertion, car [2, 2] est en dehors des limites de la matrice.

Le type Indice

Les indices d'instance, comme décrit ci-dessus, sont des indices que vous appelez sur une instance d'un type particulier. Vous pouvez également définir des indices qui sont appelés sur le type lui-même. Ce type d'indice est appelé un indice de type. Vous indiquez un indice de type en écrivant le mot-clé static avant le mot-clé subscript. Les classes peuvent utiliser le mot-clé class à la place, pour permettre aux sous-classes de remplacer l'implémentation de la superclasse de cet indice. L'exemple ci-dessous montre comment définir et appeler un indice de type :


enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}
let mars = Planet[4]
print(mars)